--[[
Name: BrownieHelper
Developed by: Sonora (The Dragon Flight)
Website: http://sonora.kirintor.googlepages.com/contents
SVN: http://code.google.com/p/browniehelper/source/browse
License: GNU General Public License v3
]]

--[[
Simply here to factor out all the stuff the application needs to do to manage
auctions for the sake of readability.  Perhaps this becomes UI modular one
day... *shrugs*

]]

--Auction states
BROWNIEHELPER_PREAUCTION = 0
BROWNIEHELPER_AUCTIONRUNNING = 1
BROWNIEHELPER_POSTAUCTION = 2
BROWNIEHELPER_AUCTIONFINALIZED = 3
BROWNIEHELPER_NOAUCTION = 4

--This is the auction sequence
function BrownieHelper:startAuction(itemInfo)
    --Called by the ViewItemsFrame when "Start auction" is clicked
    
    --get the current pool-based minimum bid, if available, otherwise default to 0
    local runName, runDb = self:getCurrentRun()
    
    --If there's no run, don't try to start an auction
    if runDb==nil then
        handler = function(response)
            if response then ViewRunsFrame:Show() end
        end
        yesnoDialog("You can only run an auction when there's a current run selected.  Would you like to open the run window to select one now?", handler)
        return false
    end
    
    local minbid = (tonumber((self:getDb().MinimumBids or {})[runDb.point_pool]) or 0)
    
    --Figure out what phase we should be in
    if itemInfo.phaseIndex==nil then itemInfo.phaseIndex = 0 end
    
    --if we can advance the phase, do so
    itemInfo.phaseIndex = itemInfo.phaseIndex + 1
    if itemInfo.biddingsystems[itemInfo.phaseIndex]~=nil then
        biddingsystem = itemInfo.biddingsystems[itemInfo.phaseIndex]
    else
        pp("Can't continue because there are no more bidding systems to choose from")
        return false
    end

    --Construct a bid setup to run the auction
    local bidSetup = {
        --Our own additions
        itemInfo = itemInfo,
        nextPhase = itemInfo.biddingsystems[itemInfo.phaseIndex+1],
        nextItem = #self:getLoot()>1,
        
        --Interface for bidding systems
        itemname = itemInfo.name, --plain item name string
        itemlink = itemInfo.link, --special id encoded name string
        
        minbid = minbid, --minimum bid, value originates with the minbid_map but may be tweaked by the user
        
        phase = itemInfo.phaseIndex, -- 1 to max phase #, nil indicates no bidding is open
        biddingsystem = biddingsystem, --bidding system currently in use
        
        app = self, --application instance currently running the bid
    }
    
    --Intialize a new bid trackers
    self.bids = {}
    self.bidItemInfo = itemInfo
    self.bidSetup = bidSetup
    self.winningBid = {}

    --Get the ball rolling by triggering the event
    self:TriggerEvent("BrownieHelper_AuctionStateChanged", BROWNIEHELPER_PREAUCTION, itemInfo, bidSetup)
    
end

function BrownieHelper:openBidding()

    --Get the ball rolling by triggering the event
    self:TriggerEvent("BrownieHelper_AuctionStateChanged", BROWNIEHELPER_AUCTIONRUNNING, self.bidItemInfo, self.bidSetup)
    
    --For those not running the addon, send a message in group
    local lines = {
        "~~ A "..self.bidSetup.biddingsystem.name.." auction is now open for "..self.bidItemInfo.link.." ~~",
        ""..(self.bidSetup.biddingsystem.instructions or "place your bid or roll now"),
    }
    
    if not (self.bidSetup.minbid==0 and self.bidSetup.maxbid==0) then table.insert(lines, self:getDb().Options['BiddingInstructions']) end
    self:sendGroupMessage(lines)
    
end

function BrownieHelper:closeBidding()

    --Get the ball rolling by triggering the event
    self:TriggerEvent("BrownieHelper_AuctionStateChanged", BROWNIEHELPER_POSTAUCTION, self.bidItemInfo, self.bidSetup)
    
    --Make an announcement
    self:sendGroupMessage("Bidding is now closed for "..self.bidItemInfo.link..".")

end

function BrownieHelper:finalizeResults()
    local winningPlayerName = self:getPlayerName(self.winningBid.charactername)
    
    --Format some out put structures
    local chatLines = {winningPlayerName.." has won the auction for "..self.bidSetup.itemInfo.link..".  Congratulatons!  The bids or rolls were:"}
    local bidEntries = {}
    local rollEntries = {}
    
    for playerName, bid in pairs(self.bids) do
        table.insert(chatLines, playerName.." "..(bid.bid and "bid "..tostring(bid.bid) or "")..((bid.bid and bid.roll) and ", " or "")..(bid.roll and "rolled "..tostring(bid.roll) or ""))
        if bid.bid then table.insert(bidEntries, playerName.." - "..tostring(bid.bid)) end
        if bid.roll then table.insert(rollEntries, playerName.." - "..tostring(bid.roll)) end
    end
    
    if self.winningBid.bid~=nil and self.winningBid.bid~=0 then table.insert(chatLines, "Winning point cost: "..tostring(self.winningBid.bid)) end
    
    --Announce the results to the group
    self:sendGroupMessage(chatLines)
    
    --Create an event entry in the database
    self:addEvent("Auction won", {
        player = winningPlayerName,
        character = self.winningBid.charactername,
        itemlink = self.bidSetup.itemlink,
        itemname = self.bidSetup.itemname,
        description = winningPlayerName.." won "..self.bidSetup.itemname.." for "..tostring(self.winningBid.bid).." points in a "..self.bidSetup.biddingsystem.name.." auction.",
        bids = valueJoin(bidEntries,", "),
        rolls = valueJoin(rollEntries, ", "),
        pointcost = tostring(self.winningBid.bid),
        biddingsystem = self.bidSetup.biddingsystem.name,
        }
    )
    
    --Subtract the winner's points and notify them of the change, if there was a point cost
    if self.winningBid.bid~=nil and self.winningBid.bid~=0 then
        local pool, pointsDb = self:getCurrentPointPool()
        local lastValue = tonumber(pointsDb[winningPlayerName])
        local newValue = lastValue - tonumber(self.winningBid.bid)
        pointsDb[winningPlayerName] = tostring(newValue)
        self:TriggerEvent("BrownieHelper_PointsChanged")
        self:sendWhisperMessage(self.winningBid.charactername, "Your points have been adjust to reflect this win.  You have "..pointsDb[winningPlayerName].. " points left.")
    end
    
    --Register the winner with the bidding system
    if self.bidSetup.biddingsystem.registerWinner then self.bidSetup.biddingsystem.registerWinner(self.winningBid, self.bidSetup) end
    
    --Attempt to assign the item
    yesnoDialog("Are you completely sure you want to assign "..self.bidSetup.itemlink.." to "..self.winningBid.charactername.."?\n\nIf you select no you will have to do the assignment manually.",
        function(response)
            if response then self:assignItem(self.bidSetup.itemlink, self.winningBid.charactername) end
        end
    )
    
    --The item is assigned so there is no next phase
    self.bidSetup.nextPhase = nil
    
    --Update the available loot list
    self:OnLootOpened()
    
    --Trigger the event for the next stage
    self:TriggerEvent("BrownieHelper_AuctionStateChanged", BROWNIEHELPER_AUCTIONFINALIZED, self.bidItemInfo, self.bidSetup)
    
end

function BrownieHelper:clearBidding()
    
    --Get the ball rolling by triggering the event
    self:TriggerEvent("BrownieHelper_AuctionStateChanged", BROWNIEHELPER_NOAUCTION, self.bidItemInfo, self.bidSetup)
    
    --Clear out the structuresq
    self.bids = {}
    self.bidItemInfo = {}
    self.bidSetup = {}
    self.winningBid = {}
    
    if self:getMode()>=BROWNIEHELPER_MASTERLOOTER then
        self:TriggerEvent("BrownieHelper_BidsChanged", {})
    end
    
end

function BrownieHelper:hasBids()
    return len(self.bids)~=0
end

--Application API
function BrownieHelper:selectWinner(characterName)
    --manual selection of the winner... if character name is nil we set to no winner
    
    if characterName ~= nil then
        local playerName = self:getPlayerName(characterName)
        local bid = self.bids[playerName]
        
        self.winningBid = {
            charactername = characterName,
            bid = bid.bid,
            roll = bid.roll,
            update = function()
                self:TriggerEvent("BrownieHelper_WinnerChanged")
            end,
        }
        
    else self.winningBid = nil
    end
    
    --should dispatch winner selected events
    self:TriggerEvent("BrownieHelper_WinnerChanged")
    
end
function BrownieHelper:findWinner()
    --automatic determination of the winner
    
    local winningBid, response = self.bidSetup.biddingsystem.findWinner(values(self.bids), self.bidSetup)
    
    if winningBid==nil then
        messageDialog("No winner was found because...\n\n"..tostring(response))
    else
        winningBid.update = function() self:TriggerEvent("BrownieHelper_WinnerChanged") end
    end
    
    self.winningBid = winningBid
    self:TriggerEvent("BrownieHelper_WinnerChanged")
    
end

function BrownieHelper:addBid(characterName, bidValue, force, private)

    local whisperTarget = characterName
    if private then whisperTarget = UnitName("player") end
    
    if self:biddingIsOpen() or force then
        --Make sure we're dealing in numbers
        bidValue = tonumber(bidValue)
        
        local biddingsystem = self.bidSetup.biddingsystem
        local playerName = self:getPlayerName(characterName)
        
        --See if we have a bid yet from this player
        if self.bids[playerName]==nil then
            --construct a new bid object
            newBid = {
                charactername = characterName,
                bid = bidValue,
                roll = nil,
                update = function() self:TriggerEvent("BrownieHelper_BidsChanged", self.bids) end,
            }
        else
            --map the existing one
            newBid = {
                charactername = characterName,
                bid = bidValue,
                roll = self.bids[playerName].roll,
                update = function() self:TriggerEvent("BrownieHelper_BidsChanged", self.bids) end,
            }
        end
        
        --If we're not in force mode, check the validity of the bid
        if not force then
            
            --Make sure their bid meets the minimum bid requirement
            local minbid = tonumber(biddingsystem.minbid(characterName, self.bidSetup))
            if minbid==nil then
                self:sendWhisperMessage(whisperTarget, "Your bid wasn't accepted because you can't place a point bid in this phase of the auction.")
                return false
            elseif bidValue < minbid then
                self:sendWhisperMessage(whisperTarget, "Your bid wasn't accepted because "..tostring(bidValue).." points is lower than your current minimum bid of "..tostring(minbid).." points.")
                return false
            end
            
            --Make sure their bid meets the maximum bid requirement
            local maxbid = tonumber(biddingsystem.maxbid(characterName, self.bidSetup))
            if maxbid~=nil and bidValue > maxbid then
                self:sendWhisperMessage(whisperTarget, "Your bid wasn't accepted because "..tostring(bidValue).." points is higher than your current maximum bid of "..tostring(maxbid).." points.")
                return false
            end
            
            --If the biding system defines check bid, call it
            if biddingsystem.checkBid~=nil then
                local result, response = biddingsystem.checkBid(newBid, self.bidSetup)
                if result==false then
                    self:sendWhisperMessage(whisperTarget, (response or "Your bid was not accepted."))
                    return false
                end
            end
            
        end
        
        --If we're still here all is well or we've been forced to accept the bid
        self.bids[playerName] = newBid
        self:TriggerEvent("BrownieHelper_BidsChanged", self.bids)
        self:sendWhisperMessage(whisperTarget, "Your bid of "..tostring(bidValue).." points for "..self.bidItemInfo.link.." was accepted.  Good luck!")
        return true
        
    else
        self:sendWhisperMessage(whisperTarget, "I can't accept your bid because there is no auction running right now.")
        return false
        
    end
    
end

function BrownieHelper:removeBid(characterName)
    --Delete the bid
    self.bids[self:getPlayerName(characterName)] = nil
    
    --Send a dispatch
    self:TriggerEvent("BrownieHelper_BidsChanged", self.bids)
    
    --Send them a confirmation
    self:sendWhisperMessage(characterName, "Your bids and rolls for the current auction have been cleared.")
end

function BrownieHelper:addRoll(characterName, roll, range)
    --We has the bid on bids by playername
    local playerName = self:getPlayerName(characterName)

    --Check to make sure the roll matches the range
    local requiredRange = {1,100}
    if self.bidSetup.biddingsystem.rollrange~=nil then requiredRange=self.bidSetup.biddingsystem.rollrange end
    if tonumber(range[1])~=tonumber(requiredRange[1]) or tonumber(range[2])~=tonumber(requiredRange[2]) then
        self:sendWhisperMessage(characterName, "Your roll of "..tostring(roll).." was rejected because it did not match the required range of "..tostring(requiredRange[1]).." to "..tostring(requiredRange[2])..".")
        return false
    end
    
    --See if we have a bid yet from this player
    if self.bids[playerName]==nil then
        --construct a new bid object
        self.bids[playerName]={
            charactername = characterName,
            bid = nil,
            roll = tonumber(roll),
            update = function() self:TriggerEvent("BrownieHelper_BidsChanged", self.bids) end,
        }
        
    --If we have, verify that they haven't rolled yet
    else
        if self.bids[playerName].roll ~= nil then
            self:sendWhisperMessage(characterName, "Your roll of "..tostring(roll).." was rejected because you have already rolled "..tostring(self.bids[playerName].roll).." in the current auction phase.")
            return false
        end
        self.bids[playerName].roll = tonumber(roll)
    end
    
    --Trigger the update
    self:TriggerEvent("BrownieHelper_BidsChanged", self.bids)
    
    --Notify the player
    self:sendWhisperMessage(characterName, "I've noted your roll of "..tostring(roll)..".  Good luck!")

end

function BrownieHelper:addLoot(lootInfo)
    table.insert(self.loot, lootInfo)
    self:reindexLoot()
    self:TriggerEvent("BrownieHelper_LootChanged")
end

function BrownieHelper:removeLoot(lootInfo)
    if lootInfo.index~=nil then
        table.remove(self.loot, lootInfo.index)
        self:reindexLoot()
        self:TriggerEvent("BrownieHelper_LootChanged")
    end
end

function BrownieHelper:reindexLoot()
    for i,itemInfo in ipairs(self:getLoot()) do
        itemInfo.index = i
    end
end

function BrownieHelper:getLoot()
    if self:getMode()>=BROWNIEHELPER_MASTERLOOTER then
        return self.loot
    else
        return (self.foreignLoot or {})
    end
end

function BrownieHelper:getPlayerBidSetup(characterName)
    --[[
    This is a scaled down and simplified version of bidsetup with attributes set
    to values for a particular player (from characterName)
    ]]
    local currentRun, pointsDb = self:getCurrentPointPool()
    local playerName = self:getPlayerName(characterName)
    
    if not self.bidSetup then return nil end
    
    local minBid = self.bidSetup.biddingsystem.minbid(characterName, self.bidSetup)
    local maxBid = self.bidSetup.biddingsystem.maxbid(characterName, self.bidSetup)
    
    return {
        playername= playerName,
        points=pointsDb[playerName],
        link=self.bidSetup.itemInfo.link,
        minbid=minBid,
        maxbid=maxBid,
        instructions=self.bidSetup.biddingsystem.instructions,
        canbid = (minBid ~= nil and not (minBid==0 and maxBid==0)),
    }
    
end

function BrownieHelper:biddingIsOpen()
    return self.auctionState==BROWNIEHELPER_AUCTIONRUNNING
end

function BrownieHelper:assignItem(itemLink, characterName)

    --Make sure we're the master looter
    if self:getMode()<BROWNIEHELPER_MASTERLOOTER then
        messageDialog("Cannot do the assignment because you're not the master looter")
        return false
    end

    --Get the item index from the loot window... fail if it isn't open
    local lootIndex = self:getLootSlotIndex(itemLink)
    if lootIndex == nil then
        messageDialog("Cannot do assignment because item "..itemLink.." could not be found in the loot window (or the window was closed).")
        return false
    end
    
    --Get the raid member index... fail if we can't
    local candidateIndex = self:getLootCandidateIndex(characterName)
    if candidateIndex == nil then
        messageDialog("Cannot do assignment because character "..characterName.." could not be found or verified in the raid.")
        return false
    end
    
    --If we're still here, do it!
    GiveMasterLoot(lootIndex, candidateIndex)
    
end

function BrownieHelper:getLootSlotIndex(itemLink)
    for index=1,GetNumLootItems() do
        if itemLink == GetLootSlotLink(index) then return index end
    end
end

function BrownieHelper:getLootCandidateIndex(characterName)
    for index=1,40 do
        if GetMasterLootCandidate(index) == characterName then return index end
    end
end

--Event handlers
function BrownieHelper:WatchRolls(message)

    if self:getMode()>=BROWNIEHELPER_MASTERLOOTER and (self.auctionState==BROWNIEHELPER_AUCTIONRUNNING or self.auctionState==BROWNIEHELPER_POSTAUCTION) then
        
        --Try to match a pattern for the roll text
        _, _, name, roll, lower, upper = string.find(message, "^(%a+) rolls (%d+) %((%d+)-(%d+)%)")
        
        --If we got a match
        if name then
            self:addRoll(name, tonumber(roll), {tonumber(lower),tonumber(upper)})
        end
    end

end

function BrownieHelper:OnLootOpened()
    --we rebuild self.loot everytime the loot window is opened
    
    self.loot = {}
    for index = 1, GetNumLootItems(), 1 do
        
        local lootInfo = {}
        local lootIcon, lootName, lootQuantity, lootRarity = GetLootSlotInfo(index)
        
        lootInfo.link = GetLootSlotLink(index)
        lootInfo.index = index
        if lootInfo.link then
            lootInfo.icon = (lootIcon or "")
            lootInfo.name = (lootName or "unknown: try looting again")
            lootInfo.quantity = (lootQuantity or "")
            lootInfo.rarity = (lootRarity or "")
            lootInfo.biddingsystems = (getBiddingSystems((lootName or "unknown"), self) or {})
            table.insert(self.loot, lootInfo)
            --Debug: pp(lootInfo.link)
        end
        
    end
    
    self:TriggerEvent("BrownieHelper_LootChanged")
    
end

